library(tidyverse)
library(tidymodels)
library(GGally)
library(plotly)
library(vip)
library(caret)
library(mgcv)

tl;dr

  1. I checked that the dataset has no NULLs or N/A values.

  2. Plotted histograms of quantitative predictors (XN). Most of the predictor distributions were similar to Gaussian distributions. Some of them had uniform distributions (X10, X11, X15).

  3. Plotted a correlation matrix. I observed strong correlations between pairs of variables such as:

    • X1/X12
    • X9/X12
    • X3/X7
    • X2/X5

    Additionally, I tried to compute the correlation ratio between the decision and quantitative variables but didn’t succeed.

  4. Plotted box plots between quantitative variables (XN) and the response variable. In my opinion, box plots were quite useful to see if classes are separable.

I observed that some variables have a large difference between their mean values and quartiles splitted per decision response. I observed such differences for the following variables: X1, X3, X7, X9, X12. My assumption is that such variables might be useful for classification.

  1. Then I calculated scatter plots for all possible pairwise combinations of XN variables. Some of them visually seemed more separable than others. I plotted the most promising ones first and the rest later.

  2. I plotted density and box plots using the caret library. These were the same type of plots as before, so no new insights except better visualization for density plots. On the density plot, you can see that X15 seems promising.

  3. Extracted feature importance from the random forest. We identified the following important features: X7, X12, X1, X9, X3, X15.

NB! Initially, I converted all quantitative variables to dummy variables before visualization section. I couldn’t spot any dependencies between nominal quantitative variables and response variables.

Additionally, I had an issue maintaining a consistent recipe in the workflow. When I saved the workflow and read it again, there was an issue with missing dummy columns (e.g., pet_dog) in the dataset. So, I had to revert the dataset transformation before visualization.


Next, I focused on models part.

I used cross-validation to evaluate the performance of the model. I also split the data 75% for training and 25% for testing.

I started with the predictors I discovered and tried manually eliminating one of them from the formula to see the impact.

Eventually, I ended up with a GAM model.

Task

The aim of the homework is to find a good classification method for the variable deciasion (having values yes and no) in terms of other variables, when the cost of assinging yes to observations with actual value no is 3 times higher than assigning no to observations with actual value yes. The training data is in hw7_train.csv.

set.seed(39692)

df = read_delim("hw7_train.csv", delim = ",")
Rows: 4000 Columns: 17
── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (4): country, pet, color, decision
dbl (13): X1, X2, X3, X4, X5, X6, X7, X9, X10, X11, X12, X15, X16

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
#TODO: inconsistency in workflow recipie
#rec = recipe(decision~.,data=df)|>step_dummy(all_nominal_predictors())
#df_baked = rec |> prep(df) |> bake(new_data=NULL)

splitted=initial_split(df,prop=0.75, strata = decision)
train_df=training(splitted)
test_df=testing(splitted)

cv_data=vfold_cv(train_df,strata=decision, v=5)

Exploration and visualization

summary(train_df)
       X1                X2                X3                X4                X5                 X6                X7              country                X9         
 Min.   :-3.5583   Min.   :-3.3861   Min.   : 0.3181   Min.   :-0.1551   Min.   :-4.53690   Min.   : 0.9562   Min.   :-3.260476   Length:2999        Min.   :-2.7866  
 1st Qu.:-1.0472   1st Qu.:-1.4529   1st Qu.: 6.5414   1st Qu.: 5.1030   1st Qu.:-0.09534   1st Qu.: 6.6631   1st Qu.: 0.001293   Class :character   1st Qu.:-0.1106  
 Median :-0.4031   Median :-0.9401   Median : 8.2556   Median : 6.4398   Median : 1.19656   Median : 8.8490   Median : 0.772772   Mode  :character   Median : 0.5729  
 Mean   :-0.4067   Mean   :-0.9423   Mean   : 8.2812   Mean   : 6.4541   Mean   : 1.19540   Mean   : 8.8060   Mean   : 0.766059                      Mean   : 0.5873  
 3rd Qu.: 0.2454   3rd Qu.:-0.4490   3rd Qu.: 9.9920   3rd Qu.: 7.8085   3rd Qu.: 2.43014   3rd Qu.:11.0031   3rd Qu.: 1.512864                      3rd Qu.: 1.2942  
 Max.   : 2.5902   Max.   : 1.6795   Max.   :15.9472   Max.   :13.3344   Max.   : 7.77809   Max.   :16.2009   Max.   : 4.285420                      Max.   : 3.9140  
      X10              X11             X12              pet               color                X15               X16             decision        
 Min.   :-1.331   Min.   :1.146   Min.   : 0.9767   Length:2999        Length:2999        Min.   :-12.374   Min.   : 0.09739   Length:2999       
 1st Qu.: 1.684   1st Qu.:3.450   1st Qu.: 6.1363   Class :character   Class :character   1st Qu.: -7.899   1st Qu.: 4.20628   Class :character  
 Median : 4.074   Median :5.347   Median : 7.5989   Mode  :character   Mode  :character   Median : -4.443   Median : 5.62692   Mode  :character  
 Mean   : 4.088   Mean   :5.260   Mean   : 7.6059                                         Mean   : -4.458   Mean   : 5.75933                     
 3rd Qu.: 6.452   3rd Qu.:7.058   3rd Qu.: 9.0908                                         3rd Qu.: -1.008   3rd Qu.: 7.18794                     
 Max.   : 9.539   Max.   :9.349   Max.   :16.0386                                         Max.   :  3.372   Max.   :13.83966                     
head(train_df)
str(train_df)
spc_tbl_ [2,999 × 17] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ X1      : num [1:2999] 0.2655 0.0639 0.123 -0.2313 -0.7242 ...
 $ X2      : num [1:2999] -1.138 -1.43 -1.604 -1.322 -0.996 ...
 $ X3      : num [1:2999] 5.67 6.86 7.3 9.93 4.76 ...
 $ X4      : num [1:2999] 3.1 4.95 4.14 8.73 6.36 ...
 $ X5      : num [1:2999] 0.761 0.345 1.251 0.385 1.077 ...
 $ X6      : num [1:2999] 4.48 8.47 8.57 7.79 9.89 ...
 $ X7      : num [1:2999] -0.794 -0.958 0.668 0.531 -1.44 ...
 $ country : chr [1:2999] "Latvia" "Estonia" "Latvia" "Estonia" ...
 $ X9      : num [1:2999] -0.58 -0.415 0.955 2.67 2.234 ...
 $ X10     : num [1:2999] 8.0725 8.9134 8.5025 -0.0426 0.1185 ...
 $ X11     : num [1:2999] 2.03 5.72 7.54 5.23 6.2 ...
 $ X12     : num [1:2999] 9.62 9.21 6.25 6.97 6.05 ...
 $ pet     : chr [1:2999] "snake" "dog" "cat" "hamster" ...
 $ color   : chr [1:2999] "red" "blue" "blue" "blue" ...
 $ X15     : num [1:2999] -2.86 -10.55 -10.08 -2.57 -4.07 ...
 $ X16     : num [1:2999] 5.48 2.35 6.92 4.64 4.63 ...
 $ decision: chr [1:2999] "no" "no" "no" "no" ...
 - attr(*, "spec")=
  .. cols(
  ..   X1 = col_double(),
  ..   X2 = col_double(),
  ..   X3 = col_double(),
  ..   X4 = col_double(),
  ..   X5 = col_double(),
  ..   X6 = col_double(),
  ..   X7 = col_double(),
  ..   country = col_character(),
  ..   X9 = col_double(),
  ..   X10 = col_double(),
  ..   X11 = col_double(),
  ..   X12 = col_double(),
  ..   pet = col_character(),
  ..   color = col_character(),
  ..   X15 = col_double(),
  ..   X16 = col_double(),
  ..   decision = col_character()
  .. )
 - attr(*, "problems")=<externalptr> 

Decision probability we estimate from the train sample:

freq_table = table(train_df$decision)

probabilities <- freq_table / sum(freq_table)

print(probabilities)

       no       yes 
0.4908303 0.5091697 
probability_no = probabilities[1]
probability_yes = probabilities[2]
hist(train_df$X1, main = "Distribution of X1", xlab = "X1")

hist(train_df$X2, main = "Distribution of X2", xlab = "X2")

hist(train_df$X3, main = "Distribution of X3", xlab = "X3")

hist(train_df$X4, main = "Distribution of X4", xlab = "X4")

hist(train_df$X5, main = "Distribution of X5", xlab = "X5")

hist(train_df$X6, main = "Distribution of X6", xlab = "X6")

hist(train_df$X7, main = "Distribution of X7", xlab = "X7")

hist(train_df$X9, main = "Distribution of X9", xlab = "X9")

hist(train_df$X10, main = "Distribution of X10", xlab = "X10")

hist(train_df$X11, main = "Distribution of X11", xlab = "X11")

hist(train_df$X12, main = "Distribution of X12", xlab = "X12")

hist(train_df$X15, main = "Distribution of X15", xlab = "X15")

hist(train_df$X16, main = "Distribution of X16", xlab = "X16")

ggcorr(train_df[1:14], 
       label = TRUE,                 # Add correlation values
       label_round = 2,              # Round values to 2 decimal places
       hjust = 1,                    # Adjust label alignment
       low = "blue",                 # Color for negative correlations
       mid = "white",                # Color for neutral correlations
       high = "red",                 # Color for positive correlations
       label_size = 2                # Increase label text size
)
Warning in ggcorr(train_df[1:14], label = TRUE, label_round = 2, hjust = 1,  :
  data in column(s) 'country', 'pet', 'color' are not numeric and were ignored

More or less promising predictors in terms of separability for decision respoinse:

All pairwise combinations scatter plots

ggplot(train_df) + geom_point(aes(x = X1, y = X2, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X3, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X4, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X5, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X6, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X7, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X9, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X1, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X3, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X4, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X5, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X6, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X7, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X9, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X2, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X4, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X5, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X6, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X7, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X9, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X3, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X5, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X6, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X7, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X9, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X4, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X5, y = X6, col = decision))

ggplot(train_df) + geom_point(aes(x = X5, y = X7, col = decision))

ggplot(train_df) + geom_point(aes(x = X5, y = X9, col = decision))

ggplot(train_df) + geom_point(aes(x = X5, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X5, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X5, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X5, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X5, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X7, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X9, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X7, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X9, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X6, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X7, y = X9, col = decision))

ggplot(train_df) + geom_point(aes(x = X7, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X7, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X7, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X7, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X7, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X9, y = X10, col = decision))

ggplot(train_df) + geom_point(aes(x = X9, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X9, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X9, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X9, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X10, y = X11, col = decision))

ggplot(train_df) + geom_point(aes(x = X10, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X10, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X10, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X11, y = X12, col = decision))

ggplot(train_df) + geom_point(aes(x = X11, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X11, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X12, y = X15, col = decision))

ggplot(train_df) + geom_point(aes(x = X12, y = X16, col = decision))

ggplot(train_df) + geom_point(aes(x = X15, y = X16, col = decision))

table(train_df$pet)

    cat     dog hamster   snake  turtle 
    324     313     858     916     588 
table(train_df$color)

 blue green   red 
 1503   589   907 
table(train_df$country)

  Estonia    Latvia Lithuania 
     1476      1244       279 
table(train_df$decision)

  no  yes 
1472 1527 
featurePlot(x = train_df[, 1:3], 
            y = train_df$decision, 
            plot = "pairs",
            ## Add a key at the top
            auto.key = list(columns = 3))
Warning in panel.xyplot(..., identifier = identifier) :
  NAs introduced by coercion
Warning in panel.xyplot(..., identifier = identifier) :
  NAs introduced by coercion
Warning in panel.xyplot(..., identifier = identifier) :
  NAs introduced by coercion
Warning in panel.xyplot(..., identifier = identifier) :
  NAs introduced by coercion
Warning in panel.xyplot(..., identifier = identifier) :
  NAs introduced by coercion
Warning in panel.xyplot(..., identifier = identifier) :
  NAs introduced by coercion

featurePlot(x = train_df[, c(1,2,3,4,5,6,7,9,10,11,12,15,16)], 
            y = factor(train_df$decision),
            plot = "density", 
            ## Pass in options to xyplot() to 
            ## make it prettier
            scales = list(x = list(relation="free"), 
                          y = list(relation="free")), 
            adjust = 1.5, 
            pch = "|", 
            layout = c(4, 1), 
            auto.key = list(columns = 3))
Warning in draw.key(simpleKey(...), draw = FALSE) :
  not enough rows for columns

featurePlot(x = train_df[, c(1,2,3,4,5,6,7,9,10,11,12,15,16)], 
            y = factor(train_df$decision),
            plot = "box", 
            ## Pass in options to bwplot() 
            scales = list(y = list(relation="free"),
                          x = list(rot = 90)),  
            layout = c(4,1 ), 
            auto.key = list(columns = 2))

train_df <- train_df %>% mutate(across(all_of(skewed_vars), ~ log1p(.)))
Warning: There were 2 warnings in `mutate()`.
The first warning was:
ℹ In argument: `across(all_of(skewed_vars), ~log1p(.))`.
Caused by warning in `log1p()`:
! NaNs produced
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 1 remaining warning.
# Correlation heatmap
correlation_matrix <- train_df %>%
  select(-decision) %>%
  cor()
Error in cor(.) : 'x' must be numeric

Find the best model

GAM

summary(train_df)
       X1                X2                X3                X4                X5                 X6                X7              country                X9         
 Min.   :-3.5583   Min.   :-3.3861   Min.   : 0.3181   Min.   :-0.1551   Min.   :-4.53690   Min.   : 0.9562   Min.   :-3.260476   Length:2999        Min.   :-2.7866  
 1st Qu.:-1.0472   1st Qu.:-1.4529   1st Qu.: 6.5414   1st Qu.: 5.1030   1st Qu.:-0.09534   1st Qu.: 6.6631   1st Qu.: 0.001293   Class :character   1st Qu.:-0.1106  
 Median :-0.4031   Median :-0.9401   Median : 8.2556   Median : 6.4398   Median : 1.19656   Median : 8.8490   Median : 0.772772   Mode  :character   Median : 0.5729  
 Mean   :-0.4067   Mean   :-0.9423   Mean   : 8.2812   Mean   : 6.4541   Mean   : 1.19540   Mean   : 8.8060   Mean   : 0.766059                      Mean   : 0.5873  
 3rd Qu.: 0.2454   3rd Qu.:-0.4490   3rd Qu.: 9.9920   3rd Qu.: 7.8085   3rd Qu.: 2.43014   3rd Qu.:11.0031   3rd Qu.: 1.512864                      3rd Qu.: 1.2942  
 Max.   : 2.5902   Max.   : 1.6795   Max.   :15.9472   Max.   :13.3344   Max.   : 7.77809   Max.   :16.2009   Max.   : 4.285420                      Max.   : 3.9140  
      X10              X11             X12              pet               color                X15               X16             decision             X1_X12       
 Min.   :-1.331   Min.   :1.146   Min.   : 0.9767   Length:2999        Length:2999        Min.   :-12.374   Min.   : 0.09739   Length:2999        Min.   :-19.528  
 1st Qu.: 1.684   1st Qu.:3.450   1st Qu.: 6.1363   Class :character   Class :character   1st Qu.: -7.899   1st Qu.: 4.20628   Class :character   1st Qu.: -6.448  
 Median : 4.074   Median :5.347   Median : 7.5989   Mode  :character   Mode  :character   Median : -4.443   Median : 5.62692   Mode  :character   Median : -2.801  
 Mean   : 4.088   Mean   :5.260   Mean   : 7.6059                                         Mean   : -4.458   Mean   : 5.75933                      Mean   : -1.662  
 3rd Qu.: 6.452   3rd Qu.:7.058   3rd Qu.: 9.0908                                         3rd Qu.: -1.008   3rd Qu.: 7.18794                      3rd Qu.:  2.015  
 Max.   : 9.539   Max.   :9.349   Max.   :16.0386                                         Max.   :  3.372   Max.   :13.83966                      Max.   : 33.444  
     X9_X12       
 Min.   :-33.851  
 1st Qu.: -0.941  
 Median :  4.097  
 Mean   :  2.727  
 3rd Qu.:  7.816  
 Max.   : 23.803  
formula = decision~s(X7) + s(X12) + s(X9) + s(X1) + s(X3) + s(X15) + s(X1_X12) + s(X9_X12)

rec=recipe(decision~.,data=train_df) |> step_dummy(all_nominal_predictors())

wf_gam=workflow()|>add_recipe(rec)|>
  add_model(gen_additive_mod(mode="classification"),formula=formula)

res_tune_gam=tune_grid(wf_gam, resamples=cv_data, metrics =metric_set(mn_log_loss))
Warning: No tuning parameters have been detected, performance will be evaluated using the resamples with no tuning. Did you want to [tune()] parameters?
→ A | error:   Not all variables in the recipe are present in the supplied training set: `X1_X12` and `X9_X12`.

There were issues with some computations   A: x1

There were issues with some computations   A: x2

There were issues with some computations   A: x3

There were issues with some computations   A: x5

There were issues with some computations   A: x5
Warning: All models failed. Run `show_notes(.Last.tune.result)` for more information.
show_best(res_tune_gam,metric="mn_log_loss")
Error in `estimate_tune_results()`:
! All models failed. Run `show_notes(.Last.tune.result)` for more information.
Backtrace:
 1. tune::show_best(res_tune_gam, metric = "mn_log_loss")
 2. tune:::show_best.tune_results(res_tune_gam, metric = "mn_log_loss")
 3. tune::.filter_perf_metrics(x, metric, eval_time)
 4. tune::estimate_tune_results(x)
predictions_gam <- augment(wf_final_gam, new_data = test_df)

predictions_gam$decision = factor(predictions_gam$decision)

decision_lvls = levels(predictions_gam$decision)
stopifnot(decision_lvls == c('no','yes'))
decision_yes_level = if(decision_lvls[2] == 'yes')'second'else'first'
stopifnot(decision_yes_level == 'second')

roc_gam=predictions_gam|>roc_curve(decision,.pred_yes,event_level=decision_yes_level)
plot1=autoplot(roc_gam)
plot1

When the cost of assigning yes to observations with actual value no is 3 times higher than assigning no to observations with actual value yes.

min_loss_gam = roc_gam|>mutate(x=1-specificity,y=sensitivity,fn_value=3*probability_no*x+probability_yes*(1-y))|>filter(fn_value==min(fn_value))
print(min_loss_gam)

threashold_gam = min_loss_gam$.threshold
print(predictions_gam |> conf_mat(truth = decision, estimate = .pred_class))

predictions_after_cutoff_gam = predictions_gam |> mutate(.pred_class=factor(if_else(.pred_yes>threashold_gam,"yes","no"),levels=decision_lvls))

conf_matrix <- predictions_after_cutoff_gam |> conf_mat(truth = decision, estimate = .pred_class)

print(conf_matrix)

accuracy_score <- predictions_after_cutoff_gam |> accuracy(truth = decision, estimate = .pred_class)

print(accuracy_score)

Random forest

# Recipe
rec <- recipe(
  decision ~X7+X12+X9+X1+X3+X15,
  data = train_df)

# Workflow for Random Forest
wf_rf <- workflow() |>
  add_recipe(rec) |>
  add_model(rand_forest(mode = "classification", mtry = tune(), trees = 512))

# Tuning the Random Forest model
res_tune_rf <- tune_grid(
  wf_rf,
  resamples = cv_data,
  metrics = metric_set(mn_log_loss),
  grid = 4 # You can adjust the number of grid points if needed
)

show_best(res_tune_rf, metric = "mn_log_loss")

wf_final_rf <- finalize_workflow(wf_rf, select_best(res_tune_rf, metric='mn_log_loss')) |>
  fit(data = train_df)
predictions_rf <- augment(wf_final_rf, new_data = test_df)

predictions_rf$decision = factor(predictions_rf$decision)

roc_rf=predictions_rf|>roc_curve(decision,.pred_yes,event_level=decision_yes_level)

plot_gam_rf = plot1+geom_path(data=roc_rf,aes(x=1-specificity,y=sensitivity),color="red")
plot_gam_rf

When the cost of assigning yes to observations with actual value no is 3 times higher than assigning no to observations with actual value yes.

min_loss_rf = roc_rf|>mutate(x=1-specificity,y=sensitivity,fn_value=3*probability_no*x+probability_yes*(1-y))|>filter(fn_value==min(fn_value))
print(min_loss_rf)

threashold_rf = min_loss_rf$.threshold
print(predictions_rf |> conf_mat(truth = decision, estimate = .pred_class))

predictions_after_cutoff_rf = predictions_rf |> mutate(.pred_class=factor(if_else(.pred_yes>threashold_rf,"yes","no"),levels=decision_lvls))

conf_matrix <- predictions_after_cutoff_rf |> conf_mat(truth = decision, estimate = .pred_class)

print(conf_matrix)

accuracy_score <- predictions_after_cutoff_rf |> accuracy(truth = decision, estimate = .pred_class)

print(accuracy_score)
library(randomForest)



# Fit the Random Forest model
rf_model <- randomForest(
  decision ~ ., 
  data = recipe(decision~.,data=train_df) |> step_dummy(all_nominal_predictors()) |> prep(data=train_df) |> bake(new_data=NULL), 
  importance = TRUE, ntree = 500)

# Print the model summary
print(rf_model)

# Calculate feature importance
importance <- importance(rf_model)
importance_df <- data.frame(Feature = rownames(importance), Importance = importance[, 1])

# Print feature importance
print(importance_df)

ggplot(importance_df, aes(x = reorder(Feature, Importance), y = Importance)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  theme_minimal() +
  labs(title = "Feature Importance for Random Forest Model",
       x = "Feature",
       y = "Importance")

SVM

rec <- recipe(decision ~ X7+X12+X9+X1+X3+X15, data = train_df)

# Workflow for SVM
wf_svm <- workflow() |>
  add_recipe(rec) |>
  add_model(
    #svm_linear(mode="classification",cost=tune()) |> set_engine("kernlab",scaled=TRUE)
    svm_poly(mode="classification",cost=tune(),engine="kernlab",degree=tune()) |> set_engine("kernlab", prob.model = TRUE)
    )

cost_grid=expand_grid(cost=c(0.001, 0.01, 1),degree=2:3)

# Tuning the SVM model
res_tune_svm <- tune_grid(
  wf_svm,
  resamples = cv_data,
  metrics = metric_set(mn_log_loss),
  grid = cost_grid
)

show_best(res_tune_svm, metric = "mn_log_loss")

wf_final_svm <- finalize_workflow(
  wf_svm,
  select_best(res_tune_svm, metric = "mn_log_loss")
) |>
  fit(data = train_df)
predictions_svm <- augment(wf_final_svm, new_data = test_df)

predictions_svm$decision = factor(predictions_svm$decision)

roc_svm=predictions_svm|>roc_curve(decision,.pred_yes,event_level=decision_yes_level)

plot_gam_rf+geom_path(data=roc_svm,aes(x=1-specificity,y=sensitivity),color="blue")

When the cost of assigning yes to observations with actual value no is 3 times higher than assigning no to observations with actual value yes.

min_loss_svm = roc_svm|>mutate(x=1-specificity,y=sensitivity,fn_value=3*probability_no*x+probability_yes*(1-y))|>filter(fn_value==min(fn_value))
print(min_loss_svm)

threashold_svm = min_loss_svm$.threshold
print(predictions_svm |> conf_mat(truth = decision, estimate = .pred_class))

predictions_after_cutoff_svm = predictions_svm |> mutate(.pred_class=factor(if_else(.pred_yes>threashold_svm,"yes","no"),levels=decision_lvls))

conf_matrix <- predictions_after_cutoff_svm |> conf_mat(truth = decision, estimate = .pred_class)

print(conf_matrix)

accuracy_score <- predictions_after_cutoff_svm |> accuracy(truth = decision, estimate = .pred_class)

print(accuracy_score)

Final

sprintf("FINAL THRESHOLD: %f", threashold_gam)
sprintf("DECISION LVLS: %s", decision_lvls)

fitted_workflow <- finalize_workflow(wf_gam, select_best(res_tune_gam, metric='mn_log_loss')) |> fit(data = df)

my_predictions <- function(wf, new_data) {
  predictions <- augment(wf, new_data = new_data)
  predictions <- predictions |>
    mutate(.pred_class = factor(
      if_else(.pred_yes > 0.816406, "yes", "no"),
      levels = c('no', 'yes')
    ))
  return(predictions$.pred_class)
}

save(
  model_classification = fitted_workflow,
  prediction_function = my_predictions,
  file = "ploter_hw7.Rdata"
)
LS0tCnRpdGxlOiAiSFcwN19QbG90ZXIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeShwbG90bHkpCmxpYnJhcnkodmlwKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KG1nY3YpCmBgYAoKIyB0bDtkcgoKMC4gSSBjaGVja2VkIHRoYXQgdGhlIGRhdGFzZXQgaGFzIG5vIE5VTExzIG9yIE4vQSB2YWx1ZXMuCgoxLiBQbG90dGVkIGhpc3RvZ3JhbXMgb2YgcXVhbnRpdGF0aXZlIHByZWRpY3RvcnMgKFhOKS4gTW9zdCBvZiB0aGUgcHJlZGljdG9yIGRpc3RyaWJ1dGlvbnMgd2VyZSBzaW1pbGFyIHRvIEdhdXNzaWFuIGRpc3RyaWJ1dGlvbnMuIFNvbWUgb2YgdGhlbSBoYWQgdW5pZm9ybSBkaXN0cmlidXRpb25zIChYMTAsIFgxMSwgWDE1KS4KCjIuIFBsb3R0ZWQgYSBjb3JyZWxhdGlvbiBtYXRyaXguIEkgb2JzZXJ2ZWQgc3Ryb25nIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHBhaXJzIG9mIHZhcmlhYmxlcyBzdWNoIGFzOgogICAtIFgxL1gxMgogICAtIFg5L1gxMgogICAtIFgzL1g3CiAgIC0gWDIvWDUKCiAgIEFkZGl0aW9uYWxseSwgSSB0cmllZCB0byBjb21wdXRlIHRoZSBjb3JyZWxhdGlvbiByYXRpbyBiZXR3ZWVuIHRoZSBkZWNpc2lvbiBhbmQgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcyBidXQgZGlkbid0IHN1Y2NlZWQuCgozLiBQbG90dGVkIGJveCBwbG90cyBiZXR3ZWVuIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgKFhOKSBhbmQgdGhlIHJlc3BvbnNlIHZhcmlhYmxlLiAKSW4gbXkgb3BpbmlvbiwgYm94IHBsb3RzIHdlcmUgcXVpdGUgdXNlZnVsIHRvIHNlZSBpZiBjbGFzc2VzIGFyZSBzZXBhcmFibGUuIAoKSSBvYnNlcnZlZCB0aGF0IHNvbWUgdmFyaWFibGVzIGhhdmUgYSBsYXJnZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlaXIgbWVhbiB2YWx1ZXMgYW5kIHF1YXJ0aWxlcyBzcGxpdHRlZCBwZXIgZGVjaXNpb24gcmVzcG9uc2UuIApJIG9ic2VydmVkIHN1Y2ggZGlmZmVyZW5jZXMgZm9yIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOiBYMSwgWDMsIFg3LCBYOSwgWDEyLiBNeSBhc3N1bXB0aW9uIGlzIHRoYXQgc3VjaCB2YXJpYWJsZXMgbWlnaHQgYmUgdXNlZnVsIGZvciBjbGFzc2lmaWNhdGlvbi4KCjQuIFRoZW4gSSBjYWxjdWxhdGVkIHNjYXR0ZXIgcGxvdHMgZm9yIGFsbCBwb3NzaWJsZSBwYWlyd2lzZSBjb21iaW5hdGlvbnMgb2YgWE4gdmFyaWFibGVzLiBTb21lIG9mIHRoZW0gdmlzdWFsbHkgc2VlbWVkIG1vcmUgc2VwYXJhYmxlIHRoYW4gb3RoZXJzLiAKSSBwbG90dGVkIHRoZSBtb3N0IHByb21pc2luZyBvbmVzIGZpcnN0IGFuZCB0aGUgcmVzdCBsYXRlci4KCjUuIEkgcGxvdHRlZCBkZW5zaXR5IGFuZCBib3ggcGxvdHMgdXNpbmcgdGhlIGBjYXJldGAgbGlicmFyeS4gVGhlc2Ugd2VyZSB0aGUgc2FtZSB0eXBlIG9mIHBsb3RzIGFzIGJlZm9yZSwgc28gbm8gbmV3IGluc2lnaHRzIGV4Y2VwdCBiZXR0ZXIgdmlzdWFsaXphdGlvbiBmb3IgZGVuc2l0eSBwbG90cy4gCk9uIHRoZSBkZW5zaXR5IHBsb3QsIHlvdSBjYW4gc2VlIHRoYXQgWDE1IHNlZW1zIHByb21pc2luZy4KCjYuIEV4dHJhY3RlZCBmZWF0dXJlIGltcG9ydGFuY2UgZnJvbSB0aGUgcmFuZG9tIGZvcmVzdC4gV2UgaWRlbnRpZmllZCB0aGUgZm9sbG93aW5nIGltcG9ydGFudCBmZWF0dXJlczogWDcsIFgxMiwgWDEsIFg5LCBYMywgWDE1LgoKKipOQiEqKgpJbml0aWFsbHksIEkgY29udmVydGVkIGFsbCBxdWFudGl0YXRpdmUgdmFyaWFibGVzIHRvIGR1bW15IHZhcmlhYmxlcyBiZWZvcmUgdmlzdWFsaXphdGlvbiBzZWN0aW9uLiAKSSBjb3VsZG4ndCBzcG90IGFueSBkZXBlbmRlbmNpZXMgYmV0d2VlbiBub21pbmFsIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgYW5kIHJlc3BvbnNlIHZhcmlhYmxlcy4KCkFkZGl0aW9uYWxseSwgSSBoYWQgYW4gaXNzdWUgbWFpbnRhaW5pbmcgYSBjb25zaXN0ZW50IHJlY2lwZSBpbiB0aGUgd29ya2Zsb3cuIFdoZW4gSSBzYXZlZCB0aGUgd29ya2Zsb3cgYW5kIHJlYWQgaXQgYWdhaW4sIHRoZXJlIHdhcyBhbiBpc3N1ZSB3aXRoIG1pc3NpbmcgZHVtbXkgY29sdW1ucyAoZS5nLiwgcGV0X2RvZykgaW4gdGhlIGRhdGFzZXQuIFNvLCBJIGhhZCB0byByZXZlcnQgdGhlIGRhdGFzZXQgdHJhbnNmb3JtYXRpb24gYmVmb3JlIHZpc3VhbGl6YXRpb24uCgotLS0KCk5leHQsIEkgZm9jdXNlZCBvbiBtb2RlbHMgcGFydC4KCkkgdXNlZCBjcm9zcy12YWxpZGF0aW9uIHRvIGV2YWx1YXRlIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWwuIEkgYWxzbyBzcGxpdCB0aGUgZGF0YSA3NSUgZm9yIHRyYWluaW5nIGFuZCAyNSUgZm9yIHRlc3RpbmcuCgpJIHN0YXJ0ZWQgd2l0aCB0aGUgcHJlZGljdG9ycyBJIGRpc2NvdmVyZWQgYW5kIHRyaWVkIG1hbnVhbGx5IGVsaW1pbmF0aW5nIG9uZSBvZiB0aGVtIGZyb20gdGhlIGZvcm11bGEgdG8gc2VlIHRoZSBpbXBhY3QuCgpFdmVudHVhbGx5LCBJIGVuZGVkIHVwIHdpdGggYSBHQU0gbW9kZWwuCgoKIyBUYXNrCgpUaGUgYWltIG9mIHRoZSBob21ld29yayBpcyB0byBmaW5kIGEgZ29vZCBjbGFzc2lmaWNhdGlvbiBtZXRob2QgZm9yIHRoZSB2YXJpYWJsZSBkZWNpYXNpb24gKGhhdmluZyB2YWx1ZXMgeWVzIGFuZCBubykgaW4gdGVybXMgb2Ygb3RoZXIgdmFyaWFibGVzLCB3aGVuIHRoZSBjb3N0IG9mIGFzc2luZ2luZyB5ZXMgdG8gb2JzZXJ2YXRpb25zIHdpdGggYWN0dWFsIHZhbHVlIG5vIGlzIDMgdGltZXMgaGlnaGVyIHRoYW4gYXNzaWduaW5nIG5vIHRvIG9ic2VydmF0aW9ucyB3aXRoIGFjdHVhbCB2YWx1ZSB5ZXMuIFRoZSB0cmFpbmluZyBkYXRhIGlzIGluIGh3N190cmFpbi5jc3YuCgpgYGB7cn0Kc2V0LnNlZWQoMzk2OTIpCgpkZiA9IHJlYWRfZGVsaW0oImh3N190cmFpbi5jc3YiLCBkZWxpbSA9ICIsIikKCiNUT0RPOiBpbmNvbnNpc3RlbmN5IGluIHdvcmtmbG93IHJlY2lwaWUKI3JlYyA9IHJlY2lwZShkZWNpc2lvbn4uLGRhdGE9ZGYpfD5zdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkKI2RmX2Jha2VkID0gcmVjIHw+IHByZXAoZGYpIHw+IGJha2UobmV3X2RhdGE9TlVMTCkKCnNwbGl0dGVkPWluaXRpYWxfc3BsaXQoZGYscHJvcD0wLjc1LCBzdHJhdGEgPSBkZWNpc2lvbikKdHJhaW5fZGY9dHJhaW5pbmcoc3BsaXR0ZWQpCnRlc3RfZGY9dGVzdGluZyhzcGxpdHRlZCkKCmN2X2RhdGE9dmZvbGRfY3YodHJhaW5fZGYsc3RyYXRhPWRlY2lzaW9uLCB2PTUpCmBgYAoKCgoKIyBFeHBsb3JhdGlvbiBhbmQgdmlzdWFsaXphdGlvbgoKYGBge3J9CnN1bW1hcnkodHJhaW5fZGYpCmhlYWQodHJhaW5fZGYpCmBgYApgYGB7cn0Kc3RyKHRyYWluX2RmKQpgYGAKRGVjaXNpb24gcHJvYmFiaWxpdHkgd2UgZXN0aW1hdGUgZnJvbSB0aGUgdHJhaW4gc2FtcGxlOgoKYGBge3J9CmZyZXFfdGFibGUgPSB0YWJsZSh0cmFpbl9kZiRkZWNpc2lvbikKCnByb2JhYmlsaXRpZXMgPC0gZnJlcV90YWJsZSAvIHN1bShmcmVxX3RhYmxlKQoKcHJpbnQocHJvYmFiaWxpdGllcykKCnByb2JhYmlsaXR5X25vID0gcHJvYmFiaWxpdGllc1sxXQpwcm9iYWJpbGl0eV95ZXMgPSBwcm9iYWJpbGl0aWVzWzJdCmBgYAoKCgpgYGB7cn0KaGlzdCh0cmFpbl9kZiRYMSwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgWDEiLCB4bGFiID0gIlgxIikKaGlzdCh0cmFpbl9kZiRYMiwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgWDIiLCB4bGFiID0gIlgyIikKaGlzdCh0cmFpbl9kZiRYMywgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgWDMiLCB4bGFiID0gIlgzIikKaGlzdCh0cmFpbl9kZiRYNCwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgWDQiLCB4bGFiID0gIlg0IikKaGlzdCh0cmFpbl9kZiRYNSwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgWDUiLCB4bGFiID0gIlg1IikKaGlzdCh0cmFpbl9kZiRYNiwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgWDYiLCB4bGFiID0gIlg2IikKaGlzdCh0cmFpbl9kZiRYNywgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgWDciLCB4bGFiID0gIlg3IikKaGlzdCh0cmFpbl9kZiRYOSwgbWFpbiA9ICJEaXN0cmlidXRpb24gb2YgWDkiLCB4bGFiID0gIlg5IikKaGlzdCh0cmFpbl9kZiRYMTAsIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIFgxMCIsIHhsYWIgPSAiWDEwIikKaGlzdCh0cmFpbl9kZiRYMTEsIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIFgxMSIsIHhsYWIgPSAiWDExIikKaGlzdCh0cmFpbl9kZiRYMTIsIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIFgxMiIsIHhsYWIgPSAiWDEyIikKaGlzdCh0cmFpbl9kZiRYMTUsIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIFgxNSIsIHhsYWIgPSAiWDE1IikKaGlzdCh0cmFpbl9kZiRYMTYsIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIFgxNiIsIHhsYWIgPSAiWDE2IikKYGBgCgoKCgoKCmBgYHtyfQpnZ2NvcnIodHJhaW5fZGZbMToxNF0sIAogICAgICAgbGFiZWwgPSBUUlVFLCAgICAgICAgICAgICAgICAgIyBBZGQgY29ycmVsYXRpb24gdmFsdWVzCiAgICAgICBsYWJlbF9yb3VuZCA9IDIsICAgICAgICAgICAgICAjIFJvdW5kIHZhbHVlcyB0byAyIGRlY2ltYWwgcGxhY2VzCiAgICAgICBoanVzdCA9IDEsICAgICAgICAgICAgICAgICAgICAjIEFkanVzdCBsYWJlbCBhbGlnbm1lbnQKICAgICAgIGxvdyA9ICJibHVlIiwgICAgICAgICAgICAgICAgICMgQ29sb3IgZm9yIG5lZ2F0aXZlIGNvcnJlbGF0aW9ucwogICAgICAgbWlkID0gIndoaXRlIiwgICAgICAgICAgICAgICAgIyBDb2xvciBmb3IgbmV1dHJhbCBjb3JyZWxhdGlvbnMKICAgICAgIGhpZ2ggPSAicmVkIiwgICAgICAgICAgICAgICAgICMgQ29sb3IgZm9yIHBvc2l0aXZlIGNvcnJlbGF0aW9ucwogICAgICAgbGFiZWxfc2l6ZSA9IDIgICAgICAgICAgICAgICAgIyBJbmNyZWFzZSBsYWJlbCB0ZXh0IHNpemUKKQpgYGAKCgpgYGB7cn0KIyBCb3hwbG90cyBncm91cGVkIGJ5IGRlY2lzaW9uCmJveHBsb3QoWDEgfiBkZWNpc2lvbiwgZGF0YSA9IHRyYWluX2RmLCBtYWluID0gIkJveHBsb3Qgb2YgWDEgYnkgRGVjaXNpb24iKQpib3hwbG90KFgyIH4gZGVjaXNpb24sIGRhdGEgPSB0cmFpbl9kZiwgbWFpbiA9ICJCb3hwbG90IG9mIFgyIGJ5IERlY2lzaW9uIikKYm94cGxvdChYMyB+IGRlY2lzaW9uLCBkYXRhID0gdHJhaW5fZGYsIG1haW4gPSAiQm94cGxvdCBvZiBYMyBieSBEZWNpc2lvbiIpCmJveHBsb3QoWDQgfiBkZWNpc2lvbiwgZGF0YSA9IHRyYWluX2RmLCBtYWluID0gIkJveHBsb3Qgb2YgWDQgYnkgRGVjaXNpb24iKQpib3hwbG90KFg1IH4gZGVjaXNpb24sIGRhdGEgPSB0cmFpbl9kZiwgbWFpbiA9ICJCb3hwbG90IG9mIFg1IGJ5IERlY2lzaW9uIikKYm94cGxvdChYNiB+IGRlY2lzaW9uLCBkYXRhID0gdHJhaW5fZGYsIG1haW4gPSAiQm94cGxvdCBvZiBYNiBieSBEZWNpc2lvbiIpCmJveHBsb3QoWDcgfiBkZWNpc2lvbiwgZGF0YSA9IHRyYWluX2RmLCBtYWluID0gIkJveHBsb3Qgb2YgWDcgYnkgRGVjaXNpb24iKQpib3hwbG90KFg5IH4gZGVjaXNpb24sIGRhdGEgPSB0cmFpbl9kZiwgbWFpbiA9ICJCb3hwbG90IG9mIFg5IGJ5IERlY2lzaW9uIikKYm94cGxvdChYMTAgfiBkZWNpc2lvbiwgZGF0YSA9IHRyYWluX2RmLCBtYWluID0gIkJveHBsb3Qgb2YgWDEwIGJ5IERlY2lzaW9uIikKYm94cGxvdChYMTEgfiBkZWNpc2lvbiwgZGF0YSA9IHRyYWluX2RmLCBtYWluID0gIkJveHBsb3Qgb2YgWDExIGJ5IERlY2lzaW9uIikKYm94cGxvdChYMTIgfiBkZWNpc2lvbiwgZGF0YSA9IHRyYWluX2RmLCBtYWluID0gIkJveHBsb3Qgb2YgWDEyIGJ5IERlY2lzaW9uIikKYm94cGxvdChYMTUgfiBkZWNpc2lvbiwgZGF0YSA9IHRyYWluX2RmLCBtYWluID0gIkJveHBsb3Qgb2YgWDE1IGJ5IERlY2lzaW9uIikKYm94cGxvdChYMTYgfiBkZWNpc2lvbiwgZGF0YSA9IHRyYWluX2RmLCBtYWluID0gIkJveHBsb3Qgb2YgWDE2IGJ5IERlY2lzaW9uIikKYGBgCgpNb3JlIG9yIGxlc3MgcHJvbWlzaW5nIHByZWRpY3RvcnMgaW4gdGVybXMgb2Ygc2VwYXJhYmlsaXR5IGZvciBkZWNpc2lvbiByZXNwb2luc2U6CgpgYGB7cn0KZ2dwbG90KHRyYWluX2RmKStnZW9tX3BvaW50KGFlcyh4PVgxLHk9WDEyLCBjb2w9ZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpK2dlb21fcG9pbnQoYWVzKHg9WDcseT1YMTIsIGNvbD1kZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikrZ2VvbV9wb2ludChhZXMoeD1YMyx5PVg3LCBjb2w9ZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpK2dlb21fcG9pbnQoYWVzKHg9WDkseT1YMTIsIGNvbD1kZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikrZ2VvbV9wb2ludChhZXMoeD1YMix5PVg1LCBjb2w9ZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpK2dlb21fcG9pbnQoYWVzKHg9WDcseT1YNSwgY29sPWRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKStnZW9tX3BvaW50KGFlcyh4PVg5LHk9WDcsIGNvbD1kZWNpc2lvbikpCmBgYApBbGwgcGFpcndpc2UgY29tYmluYXRpb25zIHNjYXR0ZXIgcGxvdHMKCmBgYHtyfQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxLCB5ID0gWDIsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMSwgeSA9IFgzLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDEsIHkgPSBYNCwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxLCB5ID0gWDUsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMSwgeSA9IFg2LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDEsIHkgPSBYNywgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxLCB5ID0gWDksIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMSwgeSA9IFgxMCwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxLCB5ID0gWDExLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDEsIHkgPSBYMTIsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMSwgeSA9IFgxNSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxLCB5ID0gWDE2LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDIsIHkgPSBYMywgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgyLCB5ID0gWDQsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMiwgeSA9IFg1LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDIsIHkgPSBYNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgyLCB5ID0gWDcsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMiwgeSA9IFg5LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDIsIHkgPSBYMTAsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMiwgeSA9IFgxMSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgyLCB5ID0gWDEyLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDIsIHkgPSBYMTUsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMiwgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpgYGAKCmBgYHtyfQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgzLCB5ID0gWDQsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMywgeSA9IFg1LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDMsIHkgPSBYNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgzLCB5ID0gWDcsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMywgeSA9IFg5LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDMsIHkgPSBYMTAsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMywgeSA9IFgxMSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgzLCB5ID0gWDEyLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDMsIHkgPSBYMTUsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYMywgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg0LCB5ID0gWDUsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNCwgeSA9IFg2LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDQsIHkgPSBYNywgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg0LCB5ID0gWDksIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNCwgeSA9IFgxMCwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg0LCB5ID0gWDExLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDQsIHkgPSBYMTIsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNCwgeSA9IFgxNSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg0LCB5ID0gWDE2LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDUsIHkgPSBYNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg1LCB5ID0gWDcsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNSwgeSA9IFg5LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDUsIHkgPSBYMTAsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNSwgeSA9IFgxMSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg1LCB5ID0gWDEyLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDUsIHkgPSBYMTUsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNSwgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg2LCB5ID0gWDcsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNiwgeSA9IFg5LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDYsIHkgPSBYMTAsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNiwgeSA9IFgxMSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg2LCB5ID0gWDEyLCBjb2wgPSBkZWNpc2lvbikpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg2LCB5ID0gWDcsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNiwgeSA9IFg5LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDYsIHkgPSBYMTAsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNiwgeSA9IFgxMSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg2LCB5ID0gWDEyLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDYsIHkgPSBYMTUsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNiwgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg3LCB5ID0gWDksIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNywgeSA9IFgxMCwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg3LCB5ID0gWDExLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDcsIHkgPSBYMTIsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYNywgeSA9IFgxNSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg3LCB5ID0gWDE2LCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDksIHkgPSBYMTAsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYOSwgeSA9IFgxMSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFg5LCB5ID0gWDEyLCBjb2wgPSBkZWNpc2lvbikpCmdncGxvdCh0cmFpbl9kZikgKyBnZW9tX3BvaW50KGFlcyh4ID0gWDksIHkgPSBYMTUsIGNvbCA9IGRlY2lzaW9uKSkKZ2dwbG90KHRyYWluX2RmKSArIGdlb21fcG9pbnQoYWVzKHggPSBYOSwgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMCwgeSA9IFgxMSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMCwgeSA9IFgxMiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMCwgeSA9IFgxNSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMCwgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMSwgeSA9IFgxMiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMSwgeSA9IFgxNSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMSwgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMiwgeSA9IFgxNSwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxMiwgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpnZ3Bsb3QodHJhaW5fZGYpICsgZ2VvbV9wb2ludChhZXMoeCA9IFgxNSwgeSA9IFgxNiwgY29sID0gZGVjaXNpb24pKQpgYGAKCmBgYHtyfQp0YWJsZSh0cmFpbl9kZiRwZXQpCnRhYmxlKHRyYWluX2RmJGNvbG9yKQp0YWJsZSh0cmFpbl9kZiRjb3VudHJ5KQp0YWJsZSh0cmFpbl9kZiRkZWNpc2lvbikKYGBgCgoKCmBgYHtyfQpmZWF0dXJlUGxvdCh4ID0gdHJhaW5fZGZbLCAxOjNdLCAKICAgICAgICAgICAgeSA9IHRyYWluX2RmJGRlY2lzaW9uLCAKICAgICAgICAgICAgcGxvdCA9ICJwYWlycyIsCiAgICAgICAgICAgICMjIEFkZCBhIGtleSBhdCB0aGUgdG9wCiAgICAgICAgICAgIGF1dG8ua2V5ID0gbGlzdChjb2x1bW5zID0gMykpCmBgYApgYGB7cn0KZmVhdHVyZVBsb3QoeCA9IHRyYWluX2RmWywgYygxLDIsMyw0LDUsNiw3LDksMTAsMTEsMTIsMTUsMTYpXSwgCiAgICAgICAgICAgIHkgPSBmYWN0b3IodHJhaW5fZGYkZGVjaXNpb24pLAogICAgICAgICAgICBwbG90ID0gImRlbnNpdHkiLCAKICAgICAgICAgICAgIyMgUGFzcyBpbiBvcHRpb25zIHRvIHh5cGxvdCgpIHRvIAogICAgICAgICAgICAjIyBtYWtlIGl0IHByZXR0aWVyCiAgICAgICAgICAgIHNjYWxlcyA9IGxpc3QoeCA9IGxpc3QocmVsYXRpb249ImZyZWUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxpc3QocmVsYXRpb249ImZyZWUiKSksIAogICAgICAgICAgICBhZGp1c3QgPSAxLjUsIAogICAgICAgICAgICBwY2ggPSAifCIsIAogICAgICAgICAgICBsYXlvdXQgPSBjKDQsIDEpLCAKICAgICAgICAgICAgYXV0by5rZXkgPSBsaXN0KGNvbHVtbnMgPSAzKSkKYGBgCgpgYGB7cn0KZmVhdHVyZVBsb3QoeCA9IHRyYWluX2RmWywgYygxLDIsMyw0LDUsNiw3LDksMTAsMTEsMTIsMTUsMTYpXSwgCiAgICAgICAgICAgIHkgPSBmYWN0b3IodHJhaW5fZGYkZGVjaXNpb24pLAogICAgICAgICAgICBwbG90ID0gImJveCIsIAogICAgICAgICAgICAjIyBQYXNzIGluIG9wdGlvbnMgdG8gYndwbG90KCkgCiAgICAgICAgICAgIHNjYWxlcyA9IGxpc3QoeSA9IGxpc3QocmVsYXRpb249ImZyZWUiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICB4ID0gbGlzdChyb3QgPSA5MCkpLCAgCiAgICAgICAgICAgIGxheW91dCA9IGMoNCwxICksIAogICAgICAgICAgICBhdXRvLmtleSA9IGxpc3QoY29sdW1ucyA9IDIpKQpgYGAKCmBgYHtyfQojIEFkZGluZyBpbnRlcmFjdGlvbiB0ZXJtcyBmb3IgY29ycmVsYXRlZCB2YXJpYWJsZXMKdHJhaW5fZGYgPC0gdHJhaW5fZGYgJT4lCiAgbXV0YXRlKAogICAgWDFfWDEyID0gWDEgKiBYMTIsCiAgICBYOV9YMTIgPSBYOSAqIFgxMgogICkKCnRlc3RfZGYgPC0gdGVzdF9kZiAlPiUKICBtdXRhdGUoCiAgICBYMV9YMTIgPSBYMSAqIFgxMiwKICAgIFg5X1gxMiA9IFg5ICogWDEyCiAgKQoKIyBMb2ctdHJhbnNmb3JtIHNrZXdlZCB2YXJpYWJsZXMKc2tld2VkX3ZhcnMgPC0gYygiWDciLCAiWDkiLCAiWDEyIikKI3RyYWluX2RmIDwtIHRyYWluX2RmICU+JSBtdXRhdGUoYWNyb3NzKGFsbF9vZihza2V3ZWRfdmFycyksIH4gbG9nMXAoLikpKQojdGVzdF9kZiA8LSB0ZXN0X2RmICU+JSBtdXRhdGUoYWNyb3NzKGFsbF9vZihza2V3ZWRfdmFycyksIH4gbG9nMXAoLikpKQoKIyBVcGRhdGVkIHJlY2lwZSB3aXRoIGludGVyYWN0aW9uIHRlcm1zIGFuZCB0cmFuc2Zvcm1hdGlvbnMKcmVjIDwtIHJlY2lwZShkZWNpc2lvbiB+IC4sIGRhdGEgPSB0cmFpbl9kZikgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpICU+JQogIHN0ZXBfaW50ZXJhY3QodGVybXMgPSB+IFgxOlgxMiArIFg5OlgxMikgJT4lCiAgc3RlcF9sb2coYWxsX29mKHNrZXdlZF92YXJzKSkKYGBgCgoKYGBge3J9CiMgQ29ycmVsYXRpb24gaGVhdG1hcApjb3JyZWxhdGlvbl9tYXRyaXggPC0gdHJhaW5fZGYgJT4lCiAgc2VsZWN0KC1kZWNpc2lvbikgJT4lCiAgY29yKCkKCmNvcnJwbG90Ojpjb3JycGxvdChjb3JyZWxhdGlvbl9tYXRyaXgsIG1ldGhvZCA9ICJjb2xvciIsIHR5cGUgPSAidXBwZXIiLCB0bC5jZXggPSAwLjcpCgojIFZpc3VhbGl6aW5nIHRoZSBlZmZlY3Qgb2YgaW50ZXJhY3Rpb25zCnRyYWluX2RmICU+JQogIGdncGxvdChhZXMoeCA9IFgxX1gxMiwgeSA9IGFzLm51bWVyaWMoZGVjaXNpb24gPT0gInllcyIpKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArCiAgbGFicyh0aXRsZSA9ICJJbnRlcmFjdGlvbiBiZXR3ZWVuIFgxIGFuZCBYMTIiLCB5ID0gIkRlY2lzaW9uICh5ZXMgPSAxKSIpCmBgYAoKIyBGaW5kIHRoZSBiZXN0IG1vZGVsCgoKIyMgR0FNCgpgYGB7cn0Kc3VtbWFyeSh0cmFpbl9kZikKYGBgCgoKYGBge3J9CmZvcm11bGEgPSBkZWNpc2lvbn5zKFg3KSArIHMoWDEyKSArIHMoWDkpICsgcyhYMSkgKyBzKFgzKSArIHMoWDE1KSArIHMoWDFfWDEyKSArIHMoWDlfWDEyKQoKcmVjPXJlY2lwZShkZWNpc2lvbn4uLGRhdGE9dHJhaW5fZGYpIHw+IHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKQoKd2ZfZ2FtPXdvcmtmbG93KCl8PmFkZF9yZWNpcGUocmVjKXw+CiAgYWRkX21vZGVsKGdlbl9hZGRpdGl2ZV9tb2QobW9kZT0iY2xhc3NpZmljYXRpb24iKSxmb3JtdWxhPWZvcm11bGEpCgpyZXNfdHVuZV9nYW09dHVuZV9ncmlkKHdmX2dhbSwgcmVzYW1wbGVzPWN2X2RhdGEsIG1ldHJpY3MgPW1ldHJpY19zZXQobW5fbG9nX2xvc3MpKQoKc2hvd19iZXN0KHJlc190dW5lX2dhbSxtZXRyaWM9Im1uX2xvZ19sb3NzIikKCndmX2ZpbmFsX2dhbSA8LSBmaW5hbGl6ZV93b3JrZmxvdyh3Zl9nYW0sIHNlbGVjdF9iZXN0KHJlc190dW5lX2dhbSwgbWV0cmljPSdtbl9sb2dfbG9zcycpKSB8PgogIGZpdChkYXRhID0gdHJhaW5fZGYpCmBgYAoKCmBgYHtyfQpwcmVkaWN0aW9uc19nYW0gPC0gYXVnbWVudCh3Zl9maW5hbF9nYW0sIG5ld19kYXRhID0gdGVzdF9kZikKCnByZWRpY3Rpb25zX2dhbSRkZWNpc2lvbiA9IGZhY3RvcihwcmVkaWN0aW9uc19nYW0kZGVjaXNpb24pCgpkZWNpc2lvbl9sdmxzID0gbGV2ZWxzKHByZWRpY3Rpb25zX2dhbSRkZWNpc2lvbikKc3RvcGlmbm90KGRlY2lzaW9uX2x2bHMgPT0gYygnbm8nLCd5ZXMnKSkKZGVjaXNpb25feWVzX2xldmVsID0gaWYoZGVjaXNpb25fbHZsc1syXSA9PSAneWVzJyknc2Vjb25kJ2Vsc2UnZmlyc3QnCnN0b3BpZm5vdChkZWNpc2lvbl95ZXNfbGV2ZWwgPT0gJ3NlY29uZCcpCgpyb2NfZ2FtPXByZWRpY3Rpb25zX2dhbXw+cm9jX2N1cnZlKGRlY2lzaW9uLC5wcmVkX3llcyxldmVudF9sZXZlbD1kZWNpc2lvbl95ZXNfbGV2ZWwpCnBsb3QxPWF1dG9wbG90KHJvY19nYW0pCnBsb3QxCmBgYAoKV2hlbiB0aGUgY29zdCBvZiBhc3NpZ25pbmcgeWVzIHRvIG9ic2VydmF0aW9ucyB3aXRoIGFjdHVhbCB2YWx1ZSBubyBpcyAzIHRpbWVzIGhpZ2hlciB0aGFuIGFzc2lnbmluZyBubyB0byBvYnNlcnZhdGlvbnMgd2l0aCBhY3R1YWwgdmFsdWUgeWVzLiAKCmBgYHtyfQptaW5fbG9zc19nYW0gPSByb2NfZ2FtfD5tdXRhdGUoeD0xLXNwZWNpZmljaXR5LHk9c2Vuc2l0aXZpdHksZm5fdmFsdWU9Mypwcm9iYWJpbGl0eV9ubyp4K3Byb2JhYmlsaXR5X3llcyooMS15KSl8PmZpbHRlcihmbl92YWx1ZT09bWluKGZuX3ZhbHVlKSkKcHJpbnQobWluX2xvc3NfZ2FtKQoKdGhyZWFzaG9sZF9nYW0gPSBtaW5fbG9zc19nYW0kLnRocmVzaG9sZApgYGAKCgpgYGB7cn0KcHJpbnQocHJlZGljdGlvbnNfZ2FtIHw+IGNvbmZfbWF0KHRydXRoID0gZGVjaXNpb24sIGVzdGltYXRlID0gLnByZWRfY2xhc3MpKQoKcHJlZGljdGlvbnNfYWZ0ZXJfY3V0b2ZmX2dhbSA9IHByZWRpY3Rpb25zX2dhbSB8PiBtdXRhdGUoLnByZWRfY2xhc3M9ZmFjdG9yKGlmX2Vsc2UoLnByZWRfeWVzPnRocmVhc2hvbGRfZ2FtLCJ5ZXMiLCJubyIpLGxldmVscz1kZWNpc2lvbl9sdmxzKSkKCmNvbmZfbWF0cml4IDwtIHByZWRpY3Rpb25zX2FmdGVyX2N1dG9mZl9nYW0gfD4gY29uZl9tYXQodHJ1dGggPSBkZWNpc2lvbiwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKCnByaW50KGNvbmZfbWF0cml4KQoKYWNjdXJhY3lfc2NvcmUgPC0gcHJlZGljdGlvbnNfYWZ0ZXJfY3V0b2ZmX2dhbSB8PiBhY2N1cmFjeSh0cnV0aCA9IGRlY2lzaW9uLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQoKcHJpbnQoYWNjdXJhY3lfc2NvcmUpCmBgYAoKIyMgUmFuZG9tIGZvcmVzdAoKYGBge3J9CiMgUmVjaXBlCnJlYyA8LSByZWNpcGUoCiAgZGVjaXNpb24gflg3K1gxMitYOStYMStYMytYMTUsCiAgZGF0YSA9IHRyYWluX2RmKQoKIyBXb3JrZmxvdyBmb3IgUmFuZG9tIEZvcmVzdAp3Zl9yZiA8LSB3b3JrZmxvdygpIHw+CiAgYWRkX3JlY2lwZShyZWMpIHw+CiAgYWRkX21vZGVsKHJhbmRfZm9yZXN0KG1vZGUgPSAiY2xhc3NpZmljYXRpb24iLCBtdHJ5ID0gdHVuZSgpLCB0cmVlcyA9IDUxMikpCgojIFR1bmluZyB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbApyZXNfdHVuZV9yZiA8LSB0dW5lX2dyaWQoCiAgd2ZfcmYsCiAgcmVzYW1wbGVzID0gY3ZfZGF0YSwKICBtZXRyaWNzID0gbWV0cmljX3NldChtbl9sb2dfbG9zcyksCiAgZ3JpZCA9IDQgIyBZb3UgY2FuIGFkanVzdCB0aGUgbnVtYmVyIG9mIGdyaWQgcG9pbnRzIGlmIG5lZWRlZAopCgpzaG93X2Jlc3QocmVzX3R1bmVfcmYsIG1ldHJpYyA9ICJtbl9sb2dfbG9zcyIpCgp3Zl9maW5hbF9yZiA8LSBmaW5hbGl6ZV93b3JrZmxvdyh3Zl9yZiwgc2VsZWN0X2Jlc3QocmVzX3R1bmVfcmYsIG1ldHJpYz0nbW5fbG9nX2xvc3MnKSkgfD4KICBmaXQoZGF0YSA9IHRyYWluX2RmKQpgYGAKCmBgYHtyfQpwcmVkaWN0aW9uc19yZiA8LSBhdWdtZW50KHdmX2ZpbmFsX3JmLCBuZXdfZGF0YSA9IHRlc3RfZGYpCgpwcmVkaWN0aW9uc19yZiRkZWNpc2lvbiA9IGZhY3RvcihwcmVkaWN0aW9uc19yZiRkZWNpc2lvbikKCnJvY19yZj1wcmVkaWN0aW9uc19yZnw+cm9jX2N1cnZlKGRlY2lzaW9uLC5wcmVkX3llcyxldmVudF9sZXZlbD1kZWNpc2lvbl95ZXNfbGV2ZWwpCgpwbG90X2dhbV9yZiA9IHBsb3QxK2dlb21fcGF0aChkYXRhPXJvY19yZixhZXMoeD0xLXNwZWNpZmljaXR5LHk9c2Vuc2l0aXZpdHkpLGNvbG9yPSJyZWQiKQpwbG90X2dhbV9yZgpgYGAKCldoZW4gdGhlIGNvc3Qgb2YgYXNzaWduaW5nIHllcyB0byBvYnNlcnZhdGlvbnMgd2l0aCBhY3R1YWwgdmFsdWUgbm8gaXMgMyB0aW1lcyBoaWdoZXIgdGhhbiBhc3NpZ25pbmcgbm8gdG8gb2JzZXJ2YXRpb25zIHdpdGggYWN0dWFsIHZhbHVlIHllcy4gCgpgYGB7cn0KbWluX2xvc3NfcmYgPSByb2NfcmZ8Pm11dGF0ZSh4PTEtc3BlY2lmaWNpdHkseT1zZW5zaXRpdml0eSxmbl92YWx1ZT0zKnByb2JhYmlsaXR5X25vKngrcHJvYmFiaWxpdHlfeWVzKigxLXkpKXw+ZmlsdGVyKGZuX3ZhbHVlPT1taW4oZm5fdmFsdWUpKQpwcmludChtaW5fbG9zc19yZikKCnRocmVhc2hvbGRfcmYgPSBtaW5fbG9zc19yZiQudGhyZXNob2xkCmBgYAoKCmBgYHtyfQpwcmludChwcmVkaWN0aW9uc19yZiB8PiBjb25mX21hdCh0cnV0aCA9IGRlY2lzaW9uLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKSkKCnByZWRpY3Rpb25zX2FmdGVyX2N1dG9mZl9yZiA9IHByZWRpY3Rpb25zX3JmIHw+IG11dGF0ZSgucHJlZF9jbGFzcz1mYWN0b3IoaWZfZWxzZSgucHJlZF95ZXM+dGhyZWFzaG9sZF9yZiwieWVzIiwibm8iKSxsZXZlbHM9ZGVjaXNpb25fbHZscykpCgpjb25mX21hdHJpeCA8LSBwcmVkaWN0aW9uc19hZnRlcl9jdXRvZmZfcmYgfD4gY29uZl9tYXQodHJ1dGggPSBkZWNpc2lvbiwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKCnByaW50KGNvbmZfbWF0cml4KQoKYWNjdXJhY3lfc2NvcmUgPC0gcHJlZGljdGlvbnNfYWZ0ZXJfY3V0b2ZmX3JmIHw+IGFjY3VyYWN5KHRydXRoID0gZGVjaXNpb24sIGVzdGltYXRlID0gLnByZWRfY2xhc3MpCgpwcmludChhY2N1cmFjeV9zY29yZSkKYGBgCgoKYGBge3J9CmxpYnJhcnkocmFuZG9tRm9yZXN0KQoKCgojIEZpdCB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbApyZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QoCiAgZGVjaXNpb24gfiAuLCAKICBkYXRhID0gcmVjaXBlKGRlY2lzaW9ufi4sZGF0YT10cmFpbl9kZikgfD4gc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCkpIHw+IHByZXAoZGF0YT10cmFpbl9kZikgfD4gYmFrZShuZXdfZGF0YT1OVUxMKSwgCiAgaW1wb3J0YW5jZSA9IFRSVUUsIG50cmVlID0gNTAwKQoKIyBQcmludCB0aGUgbW9kZWwgc3VtbWFyeQpwcmludChyZl9tb2RlbCkKCiMgQ2FsY3VsYXRlIGZlYXR1cmUgaW1wb3J0YW5jZQppbXBvcnRhbmNlIDwtIGltcG9ydGFuY2UocmZfbW9kZWwpCmltcG9ydGFuY2VfZGYgPC0gZGF0YS5mcmFtZShGZWF0dXJlID0gcm93bmFtZXMoaW1wb3J0YW5jZSksIEltcG9ydGFuY2UgPSBpbXBvcnRhbmNlWywgMV0pCgojIFByaW50IGZlYXR1cmUgaW1wb3J0YW5jZQpwcmludChpbXBvcnRhbmNlX2RmKQoKZ2dwbG90KGltcG9ydGFuY2VfZGYsIGFlcyh4ID0gcmVvcmRlcihGZWF0dXJlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh0aXRsZSA9ICJGZWF0dXJlIEltcG9ydGFuY2UgZm9yIFJhbmRvbSBGb3Jlc3QgTW9kZWwiLAogICAgICAgeCA9ICJGZWF0dXJlIiwKICAgICAgIHkgPSAiSW1wb3J0YW5jZSIpCgpgYGAKCgojIyBTVk0KCmBgYHtyfQpyZWMgPC0gcmVjaXBlKGRlY2lzaW9uIH4gWDcrWDEyK1g5K1gxK1gzK1gxNSwgZGF0YSA9IHRyYWluX2RmKQoKIyBXb3JrZmxvdyBmb3IgU1ZNCndmX3N2bSA8LSB3b3JrZmxvdygpIHw+CiAgYWRkX3JlY2lwZShyZWMpIHw+CiAgYWRkX21vZGVsKAogICAgI3N2bV9saW5lYXIobW9kZT0iY2xhc3NpZmljYXRpb24iLGNvc3Q9dHVuZSgpKSB8PiBzZXRfZW5naW5lKCJrZXJubGFiIixzY2FsZWQ9VFJVRSkKICAgIHN2bV9wb2x5KG1vZGU9ImNsYXNzaWZpY2F0aW9uIixjb3N0PXR1bmUoKSxlbmdpbmU9Imtlcm5sYWIiLGRlZ3JlZT10dW5lKCkpIHw+IHNldF9lbmdpbmUoImtlcm5sYWIiLCBwcm9iLm1vZGVsID0gVFJVRSkKICAgICkKCmNvc3RfZ3JpZD1leHBhbmRfZ3JpZChjb3N0PWMoMC4wMDEsIDAuMDEsIDEpLGRlZ3JlZT0yOjMpCgojIFR1bmluZyB0aGUgU1ZNIG1vZGVsCnJlc190dW5lX3N2bSA8LSB0dW5lX2dyaWQoCiAgd2Zfc3ZtLAogIHJlc2FtcGxlcyA9IGN2X2RhdGEsCiAgbWV0cmljcyA9IG1ldHJpY19zZXQobW5fbG9nX2xvc3MpLAogIGdyaWQgPSBjb3N0X2dyaWQKKQoKc2hvd19iZXN0KHJlc190dW5lX3N2bSwgbWV0cmljID0gIm1uX2xvZ19sb3NzIikKCndmX2ZpbmFsX3N2bSA8LSBmaW5hbGl6ZV93b3JrZmxvdygKICB3Zl9zdm0sCiAgc2VsZWN0X2Jlc3QocmVzX3R1bmVfc3ZtLCBtZXRyaWMgPSAibW5fbG9nX2xvc3MiKQopIHw+CiAgZml0KGRhdGEgPSB0cmFpbl9kZikKYGBgCgoKYGBge3J9CnByZWRpY3Rpb25zX3N2bSA8LSBhdWdtZW50KHdmX2ZpbmFsX3N2bSwgbmV3X2RhdGEgPSB0ZXN0X2RmKQoKcHJlZGljdGlvbnNfc3ZtJGRlY2lzaW9uID0gZmFjdG9yKHByZWRpY3Rpb25zX3N2bSRkZWNpc2lvbikKCnJvY19zdm09cHJlZGljdGlvbnNfc3ZtfD5yb2NfY3VydmUoZGVjaXNpb24sLnByZWRfeWVzLGV2ZW50X2xldmVsPWRlY2lzaW9uX3llc19sZXZlbCkKCnBsb3RfZ2FtX3JmK2dlb21fcGF0aChkYXRhPXJvY19zdm0sYWVzKHg9MS1zcGVjaWZpY2l0eSx5PXNlbnNpdGl2aXR5KSxjb2xvcj0iYmx1ZSIpCmBgYAoKCgpXaGVuIHRoZSBjb3N0IG9mIGFzc2lnbmluZyB5ZXMgdG8gb2JzZXJ2YXRpb25zIHdpdGggYWN0dWFsIHZhbHVlIG5vIGlzIDMgdGltZXMgaGlnaGVyIHRoYW4gYXNzaWduaW5nIG5vIHRvIG9ic2VydmF0aW9ucyB3aXRoIGFjdHVhbCB2YWx1ZSB5ZXMuIAoKYGBge3J9Cm1pbl9sb3NzX3N2bSA9IHJvY19zdm18Pm11dGF0ZSh4PTEtc3BlY2lmaWNpdHkseT1zZW5zaXRpdml0eSxmbl92YWx1ZT0zKnByb2JhYmlsaXR5X25vKngrcHJvYmFiaWxpdHlfeWVzKigxLXkpKXw+ZmlsdGVyKGZuX3ZhbHVlPT1taW4oZm5fdmFsdWUpKQpwcmludChtaW5fbG9zc19zdm0pCgp0aHJlYXNob2xkX3N2bSA9IG1pbl9sb3NzX3N2bSQudGhyZXNob2xkCmBgYAoKCmBgYHtyfQpwcmludChwcmVkaWN0aW9uc19zdm0gfD4gY29uZl9tYXQodHJ1dGggPSBkZWNpc2lvbiwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykpCgpwcmVkaWN0aW9uc19hZnRlcl9jdXRvZmZfc3ZtID0gcHJlZGljdGlvbnNfc3ZtIHw+IG11dGF0ZSgucHJlZF9jbGFzcz1mYWN0b3IoaWZfZWxzZSgucHJlZF95ZXM+dGhyZWFzaG9sZF9zdm0sInllcyIsIm5vIiksbGV2ZWxzPWRlY2lzaW9uX2x2bHMpKQoKY29uZl9tYXRyaXggPC0gcHJlZGljdGlvbnNfYWZ0ZXJfY3V0b2ZmX3N2bSB8PiBjb25mX21hdCh0cnV0aCA9IGRlY2lzaW9uLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQoKcHJpbnQoY29uZl9tYXRyaXgpCgphY2N1cmFjeV9zY29yZSA8LSBwcmVkaWN0aW9uc19hZnRlcl9jdXRvZmZfc3ZtIHw+IGFjY3VyYWN5KHRydXRoID0gZGVjaXNpb24sIGVzdGltYXRlID0gLnByZWRfY2xhc3MpCgpwcmludChhY2N1cmFjeV9zY29yZSkKYGBgCiMjIEZpbmFsCgpgYGB7cn0Kc3ByaW50ZigiRklOQUwgVEhSRVNIT0xEOiAlZiIsIHRocmVhc2hvbGRfZ2FtKQpzcHJpbnRmKCJERUNJU0lPTiBMVkxTOiAlcyIsIGRlY2lzaW9uX2x2bHMpCmBgYAoKCmBgYHtyfQoKZml0dGVkX3dvcmtmbG93IDwtIGZpbmFsaXplX3dvcmtmbG93KHdmX2dhbSwgc2VsZWN0X2Jlc3QocmVzX3R1bmVfZ2FtLCBtZXRyaWM9J21uX2xvZ19sb3NzJykpIHw+IGZpdChkYXRhID0gZGYpCgpteV9wcmVkaWN0aW9ucyA8LSBmdW5jdGlvbih3ZiwgbmV3X2RhdGEpIHsKICBwcmVkaWN0aW9ucyA8LSBhdWdtZW50KHdmLCBuZXdfZGF0YSA9IG5ld19kYXRhKQogIHByZWRpY3Rpb25zIDwtIHByZWRpY3Rpb25zIHw+CiAgICBtdXRhdGUoLnByZWRfY2xhc3MgPSBmYWN0b3IoCiAgICAgIGlmX2Vsc2UoLnByZWRfeWVzID4gMC44MTY0MDYsICJ5ZXMiLCAibm8iKSwKICAgICAgbGV2ZWxzID0gYygnbm8nLCAneWVzJykKICAgICkpCiAgcmV0dXJuKHByZWRpY3Rpb25zJC5wcmVkX2NsYXNzKQp9CgpzYXZlKAogIG1vZGVsX2NsYXNzaWZpY2F0aW9uID0gZml0dGVkX3dvcmtmbG93LAogIHByZWRpY3Rpb25fZnVuY3Rpb24gPSBteV9wcmVkaWN0aW9ucywKICBmaWxlID0gInBsb3Rlcl9odzcuUmRhdGEiCikKYGBgCgo=